/**
 * @module JsonWebToken 相关处理函数
 */
import jwt from 'jsonwebtoken'
import dayjs from 'dayjs'
import { v4 as uuidv4 } from 'uuid'
import User from '@/models/mysql/User'
import UserToken from '@/models/mongo/UserToken'
import UserAuthError from '@/errors/UserAuthError'

// token 过期时间格式
export const expiredAtFormate = 'YYYY-MM-DD HH:mm:ss'
// 默认过期时间
const defaultExpiredAt = '2199-12-31 23:59:59'

// 生成随机字符串的函数
export const generateUUID = (): string => {
  return uuidv4().replace(/-/g, '').toUpperCase()
}

// 获取 token 过期时间
const getJwtExpiredInfo = (ttlStr?: string, last_expired_at?: string) => {
  if (ttlStr) {
    const ttl = +ttlStr
    if (!isNaN(ttl) && ttl > 0) {
      let expiredDate = dayjs().add(ttl, 'minutes')
      // 如果有最后过期时间则取最小值,防止token有效期超出刷新token有效期
      if (last_expired_at) {
        const lastExpiredDate = dayjs(last_expired_at, expiredAtFormate)
        expiredDate = lastExpiredDate.isAfter(expiredDate) ? expiredDate : lastExpiredDate
      }
      return { expiresIn: ttl * 60, expiredAt: expiredDate.format(expiredAtFormate) }
    }
  }
  const expiresIn = dayjs(defaultExpiredAt, expiredAtFormate).diff(dayjs(), 'seconds') + 1
  return { expiresIn, expiredAt: defaultExpiredAt }
}

// 生成 新的 用户 token 一般用于登录，jwt这里只是用来生成token，验证token也不会用jwt，而是通过mongoDB的UserToken表来验证
// 后续会采用Redis缓存来验证token
export const generateNewUserToken = async (user: User) => {
  // token 选项
  const { expiresIn, expiredAt: token_expired_at } = getJwtExpiredInfo(process.env.TOKEN_TTL)
  // token,randomKey是为了防止token在同一时间内重复
  const token = jwt.sign({ user_id: user.id, randomKey: generateUUID() }, process.env.TOKEN_SECRET || 'token_secret', {
    expiresIn
  })
  // 刷新 token
  const { expiresIn: rtExpiresIn, expiredAt: rt_expired_at } = getJwtExpiredInfo(process.env.TOKEN_REFRESH_TTL)
  // randomKey 是为了防止token在同一时间内重复
  const refresh_token = jwt.sign(
    { user_id: user.id, isRefreshToken: true, randomKey: generateUUID() },
    process.env.TOKEN_REFRESH_SECRET || 'token_refresh_secret',
    { expiresIn: rtExpiresIn }
  )
  const userToken = new UserToken()
  userToken.user_id = user.id
  userToken.token = token
  userToken.refresh_token = refresh_token
  userToken.token_expired_at = token_expired_at
  userToken.refresh_token_expired_at = rt_expired_at
  await userToken.save()
  // 返回 token给到页面
  return token
}

// 刷新用户 token 一般用于 token 过期但是仍然在刷新有效期内，jwt这里只是用来生成token，验证token也不会用jwt，而是通过mongoDB的UserToken表来验证
export const refreshUserToken = async (userToken: UserToken) => {
  // token 选项
  const { expiresIn, expiredAt } = getJwtExpiredInfo(process.env.TOKEN_TTL, userToken.refresh_token_expired_at)
  // 生成新的token
  const newToken = jwt.sign(
    { user_id: userToken.user_id, randomKey: generateUUID() },
    process.env.TOKEN_SECRET || 'token_secret',
    { expiresIn }
  )
  const newUserToken = new UserToken()
  newUserToken.user_id = userToken.user_id
  newUserToken.token = newToken
  newUserToken.refresh_token = userToken.refresh_token
  newUserToken.refreshed_by = userToken.token
  newUserToken.token_expired_at = expiredAt
  newUserToken.refresh_token_expired_at = userToken.refresh_token_expired_at
  // 将原来的token拉黑
  userToken.is_blacked = true
  try {
    await Promise.all([newUserToken.save(), userToken.save()])
    // 返回 新的token对象给到页面
    return newUserToken
  } catch (e) {
    throw new UserAuthError('用户信息异常，需要重新登录')
  }
}
